package roborally.model;

import roborally.exceptions.InvalidPositionException;
import be.kuleuven.cs.som.annotate.*;

/**
 * A class for positions determined by two coordinates.
 * 
 * @invar	...
 * 			| getX() >= 0 && getX() <= Long.MAX_VALUE
 * @invar	...
 * 			| getY() >= 0 && getY() <= Long.MAX_VAMUE
 * 
 * @version	1.0
 * @author  Pieter Robberechts	(1e Bach Informatica)
 * @author	Jeroen Sanders		(1e Bach Informatica)
 *
 */
public class Position {
	
	/**
	 * Initialize this new position with given coordinates.
	 *  
	 * @param	x
	 * 			The x-coordinate for this new position.
	 * @param 	y
	 * 			The y-coordinate for this new position.
	 * @post	...
	 * 			| new.getX() == x
	 * @post	...
	 * 			| new.getY() == y
	 * @throws	IllegalArgumentException
	 * 			The x-coordinate is invalid.
	 * 			| (x < 0 || x > Long.MAX_VALUE)
	 * @throws	IllegalArgumentException
	 * 			The y-coordinate is invalid.
	 * 			| (y < 0 || y > Long.MAX_VALUE)
	 */
	public Position(long x, long y) throws IllegalArgumentException {
		if(x < 0 || x > Long.MAX_VALUE)
			throw new IllegalArgumentException("Invalid x-coordinate!");
		if(y < 0 || y > Long.MAX_VALUE)
			throw new IllegalArgumentException("Invalid y-coordinate!");
		this.x = x;
		this.y = y;
	}
	
	/**
	 * Variable registering the x-coordinate.
	 */
	private final long x;
	
	/**
	 * Variable registering the y-coordinate.
	 */
	private final long y;
	
	
	/**
	 * Return the x-coordinate of this position. 
	 */
	@Basic @Immutable
	public long getX(){
		return x;
	}
	
	/**
	 * Return the y-coordinate of this position.
	 */
	@Basic @Immutable
	public long getY(){
		return y;
	}
	
	/**
	 * Get the neighbor position, looking in a given orientation.
	 * 
	 * @param 	orientation
	 * 			Get the neighbor of the prime position looking in this orientation.
	 * @return	...
	 * 			| if(orientation == Orientation.UP)
	 * 			|	then result == new Position(getX(), getY()-1)
	 * 			| if(orientation == Orientation.RIGHT)
	 * 			|	then result == new Position(getX()+1, getY())
	 * 			| if(orientation == Orientation.LEFT)
	 * 			|	then result == new Position(getX(), getY()+1)
	 * 			| if(orientation == Orientation.DOWN)
	 * 			|	then result == new Position(getX()-1, getY())
	 * 
	 * @throws	InvalidPositionException
	 * 			The neighbour position is invalid.
	 * 			| ((getX()+1 > Long.MAX_VALUE) || (getY()+1 > Long.MAX_VALUE)
	 * 			| || (getX()-1 < 0) || (getY()-1 < 0))
	 * 			
	 */
	public Position getNeighbour(Orientation orientation) throws InvalidPositionException{
		try {
		switch(orientation){
			case UP:
				return new Position(getX(), getY()-1);
			case RIGHT:
				return new Position(getX()+1, getY());
			case DOWN:
				return new Position(getX(), getY()+1);
			case LEFT:
				return new Position(getX()-1, getY());
		}
		} catch(IllegalArgumentException e) {
			throw new InvalidPositionException("Invalid neighbour!");
		}
		return null;
	}
	
	/**
	 * Check whether a position is oriented in a given way, to a given second position.
	 * 
	 * @param 	other
	 * 			The second position to which the first one is compared. 
	 * @param 	orientation
	 * 			The orientation which serves as comparison between both positions.
	 * @return	...
	 * 			| if(orientation == Orientation.UP)
	 * 			|	then result == this.getY() < other.getY()
	 * 			| if(orientation == Orientation.RIGHT)
	 * 			|	then result == this.getX() > other.getX()
	 * 			| if(orientation == Orientation.LEFT)
	 * 			|	then result == this.getY() > other.getY()
	 * 			| if(orientation == Orientation.DOWN)
	 * 			|	then result == this.getX() < other.getX()
	 */
	public boolean isPlacedAtOrientationOf(Position other, Orientation orientation){
		switch(orientation){
			case UP:
				return this.getY() < other.getY();
			case RIGHT:
				return this.getX() > other.getX();
			case DOWN:
				return this.getY() > other.getY();
			case LEFT:
				return this.getX() < other.getX();
		}
		return false;
	}
	
	
	/**
	 * Calculates the manhattan distance between this position and another.
	 * 
	 * @param 	other
	 * 			The other position to calculate the manhattan distance to.
	 * @return	...
	 * 			| result == Math.abs(this.getX()-other.getX()) + Math.abs(this.getY()-other.getY())
	 */
	public long getManhattanDistance(Position other){
		return Math.abs(this.getX()-other.getX()) + Math.abs(this.getY()-other.getY());
	}
	
	/**
	 * Generate a unique code for each different position.
	 * 
	 * @return	...
	 * 			| result == ( 31 + (int) (x ^ (x >>> 32)) ) * 31 + (int) (y ^ (y >>> 32))
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + (int) (x ^ (x >>> 32));
		result = prime * result + (int) (y ^ (y >>> 32));
		return result;
	}
	
	/**
	 * Check whether a position is equal to a given object (which might be a another position).
	 * 	Two positions are equal if they have the same x and y coordinate.
	 * 
	 * @return	...
	 * 			| if( this == obj )
	 * 			|	then result == true
	 * @return	...
	 * 			| if( obj == null )
	 * 			| 	then result == false
	 * @return	...
	 * 			| if ( getClass() != obj.getClass() )
	 * 			|	then result == false
	 * @return	...
	 * 			| if( obj.getClass() == Position && getX() == other.getX() && getY() == other.getY() )
	 * 			| 	then result == true
	 * 			| else
	 * 			|	result == false
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Position other = (Position) obj;
		if (getX() != other.getX())
			return false;
		if (getY() != other.getY())
			return false;
		return true;
	}
	
	/**
	 * Return a textual representation of a position.
	 * 
	 */
	@Override
	public java.lang.String toString(){
		return "(" + this.getX() + ", " + this.getY() + ")";
	}
}
